package org.sws.aop;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateType;
import org.springframework.stereotype.Component;
import org.sws.infrastructure.redis.IRedisService;
import org.sws.types.annotations.DCCValue;
import org.sws.types.annotations.RateLimiterAccessInterceptor;
import org.sws.types.common.Constants;
import org.sws.types.utils.AttrUtils;

import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.Duration;

/**
 * @Author sws
 * @Date 2025/4/18 11:54
 * @description: 限流组件切面拦截
 */
@Slf4j
@Aspect
@Component
public class RateLimiterAOP {

    @DCCValue("rateLimiterSwitch:close")
    private String rateLimiterSwitch;
    @Resource
    private IRedisService redisService;

    @Pointcut("@annotation(org.sws.types.annotations.RateLimiterAccessInterceptor)")
    public void aopPoint() {
    }

    @Around("aopPoint()")
    public Object doRateLimiter(ProceedingJoinPoint jp) throws Throwable {
        // 判断限流配置是否开启
        if (StringUtils.isBlank(rateLimiterSwitch) || !"open".equals(rateLimiterSwitch)) {
            return jp.proceed();
        }
        MethodSignature methodSignature = (MethodSignature) jp.getSignature();
        RateLimiterAccessInterceptor rateLimiterAccessInterceptor = methodSignature.getMethod().getAnnotation(RateLimiterAccessInterceptor.class);
        // 获取注解配置的限流key
        String key = rateLimiterAccessInterceptor.key();
        if (StringUtils.isBlank(key)) {
            throw new RuntimeException("annotation RateLimiter uId is null！");
        }
        // 获取拦截字段值
        String keyAttrValue = AttrUtils.getAttrValue(key, jp.getArgs());
        log.info("aop keyAttrValue {}", keyAttrValue);

        String rateLimiterCountKey = Constants.RedisKey.RATE_LIMITER_BLACKLIST_COUNT_KEY + keyAttrValue;

        if(!"all".equals(keyAttrValue) && rateLimiterAccessInterceptor.blacklistCount() != 0){
            boolean exists = redisService.getRAtomicLong(rateLimiterCountKey).isExists();
            Long atomicLong = redisService.getAtomicLong(rateLimiterCountKey);
            if(exists && atomicLong > rateLimiterAccessInterceptor.blacklistCount()){
                log.info("redis [{}] 限流 黑名单拦截(24h): {}",keyAttrValue, rateLimiterCountKey);
                return fallbackMethodResult(jp, rateLimiterAccessInterceptor.fallbackMethod());
            }else if(!exists){
                redisService.getRAtomicLong(rateLimiterCountKey).expire(Duration.ofHours(24));
            }
        }
        RRateLimiter rateLimiter = redisService.getRateLimiter(keyAttrValue);
        if(!rateLimiter.isExists()){
            rateLimiter.trySetRate(RateType.PER_CLIENT, rateLimiterAccessInterceptor.permitsPerSecond(),Duration.ofMinutes(1));
        }
        // 获取用户限流器 - 限流拦截
        if(!rateLimiter.tryAcquire()){
            if(rateLimiterAccessInterceptor.blacklistCount() != 0){
                redisService.incr(rateLimiterCountKey);
            }
            log.info("redis 限流{}次, 拦截信息: {} ", rateLimiterAccessInterceptor.permitsPerSecond(), keyAttrValue);
            return fallbackMethodResult(jp, rateLimiterAccessInterceptor.fallbackMethod());
        }
        return jp.proceed();
    }

    /**
     * 调用用户配置的回调方法，当拦截后，返回回调结果。
     */
    private Object fallbackMethodResult(JoinPoint jp, String fallbackMethod) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        Method method = jp.getTarget().getClass().getMethod(fallbackMethod, methodSignature.getParameterTypes());
        return method.invoke(jp.getThis(), jp.getArgs());
    }
}
